package cn.com.victorysoft.ylgl.service.commons.mybatis.paging;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.function.Supplier;

import cn.com.victorysoft.ylgl.service.commons.mybatis.paging.dialect.AbstractDialect;
import cn.com.victorysoft.ylgl.service.commons.mybatis.paging.dialect.support.HSQLDialect;
import cn.com.victorysoft.ylgl.service.commons.mybatis.paging.dialect.support.MySQLDialect;
import cn.com.victorysoft.ylgl.service.commons.mybatis.paging.dialect.support.OracleDialect;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.PatternMatchUtils;

/***
 * @author gechaoqing
 */
@Intercepts({@org.apache.ibatis.plugin.Signature(type=StatementHandler.class, method="prepare", args={Connection.class})})
public class PaginationInterceptor
        implements Interceptor
{
    private static final Logger logger = LoggerFactory.getLogger(PaginationInterceptor.class);
    private static final ThreadLocal<Integer> PAGINATION_TOTAL = ThreadLocal.withInitial(()->0);
    private String stmtIdRegex = "*.selectPaging*";
    private Map<String, AbstractDialect> dialectMap = new HashMap<>();

    public static int getPaginationTotal()
    {
        return PAGINATION_TOTAL.get();
    }

    public static void clean()
    {
        PAGINATION_TOTAL.remove();
    }

    @Override
    public Object intercept(Invocation invocation)
            throws Throwable
    {
        Connection connection = (Connection)invocation.getArgs()[0];
        StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
        MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
        RowBounds rowBounds = (RowBounds)metaStatementHandler.getValue("delegate.rowBounds");
        MappedStatement mappedStatement = (MappedStatement)metaStatementHandler.getValue("delegate.mappedStatement");

        int offset = rowBounds.getOffset();
        int limit = rowBounds.getLimit();

        logger.debug("statement: {}", mappedStatement.getId());
        logger.debug("offset: {} , limit: {}", offset, limit);

        boolean isIntercept = PatternMatchUtils.simpleMatch(this.stmtIdRegex, mappedStatement.getId());
        logger.debug("pattern: {}", this.stmtIdRegex);
        logger.debug("isIntercept: {}", isIntercept);

        AbstractDialect dialect = getDialect(connection);
        boolean isInterceptOk = (isIntercept) && (dialect != null)
                && (dialect.supportsLimit())
                && ((offset != 0)
                || (limit != Integer.MAX_VALUE));
        if (isInterceptOk)
        {
            BoundSql boundSql = statementHandler.getBoundSql();
            Object parameterObject = boundSql.getParameterObject();
            int count = CountHelper.getCount(mappedStatement, connection, parameterObject, dialect);
            PAGINATION_TOTAL.set(count);

            String originalSql = (String)metaStatementHandler.getValue("delegate.boundSql.sql");
            metaStatementHandler.setValue("delegate.boundSql.sql", dialect.getLimitString(originalSql, offset, limit));
            metaStatementHandler.setValue("delegate.rowBounds.offset", 0);
            metaStatementHandler.setValue("delegate.rowBounds.limit", Integer.MAX_VALUE);
            logger.debug("limit sql: {}", boundSql.getSql());
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target)
    {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties)
    {
        this.stmtIdRegex = properties.getProperty("stmtIdRegex", "*.selectPaging*");
    }

    private AbstractDialect getDialect(Connection connection)
            throws SQLException
    {
        String jdbcUrl = connection.getMetaData().getURL();
        Database db = Database.forJdbcUrl(jdbcUrl);
        if (db == null)
        {
            String message = "暂时不支持该数据库 %s";
            logger.debug(message);
            return null;
        }
        if (this.dialectMap.containsKey(db.toString())) {
            return this.dialectMap.get(db.toString());
        }
        switch (db)
        {
            case mysql:
                AbstractDialect mysql = new MySQLDialect();
                this.dialectMap.put(db.toString(), mysql);
                return mysql;
            case oracle:
                AbstractDialect oracle = new OracleDialect();
                this.dialectMap.put(db.toString(), oracle);
                return oracle;
            case hsqldb:
                AbstractDialect hsqldb = new HSQLDialect();
                this.dialectMap.put(db.toString(), hsqldb);
                return hsqldb;
            default:return null;
        }
    }
}
